﻿using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using WebSocketSharp;
using System.Threading;
using WebSocketSharp.Server;

namespace mDaemon
{

    class Daemon
    {
        class Config
        {
            public string location;
            public int port;
            public Boolean autoStart;
            public string mainPage;
        }

        class WSCommander : WebSocketBehavior
        {
            protected override void OnMessage(MessageEventArgs e)
            {
                if (e.Data == "toogle")
                {
                    Daemon.getInstance().toogle();
                }
                else if (e.Data == "getState")
                {
                    if (Daemon.getInstance().getState())
                    {
                        Send(@"state:::started");
                    }
                    else
                    {
                        Send(@"state:::stop");
                    }
                }
            }
        }

        private Process _proc;
        private WebSocketServer _server;
        static Daemon instance;
        bool isCheck = false;
        string _exePath = "";
        string _exeArgs = "";

        public static Daemon getInstance()
        {
            if (instance == null) instance = new Daemon();
            return instance;
        }

        private Daemon()
        {
            init();
        }

        private Config config;


        void killExe(string name, string fullName)
        {
            var cur = Process.GetCurrentProcess();
            Process[] collectionOfProcess = Process.GetProcessesByName(name);
            foreach(var p in collectionOfProcess)
            {
                if (p.Id == cur.Id) continue;
                string processPath = p.MainModule.FileName;
                if (!fullName.IsNullOrEmpty())
                {
                    if (Path.GetFullPath(processPath) == fullName)
                    {
                        if (p != null)
                        {
                            p.Kill();
                        }
                    }
                } else
                {
                    p.Kill();
                }
            }
        }

        void loadConfig()
        {
            try
            {
                // load option
                string envPath = Environment.CurrentDirectory;
                string path = Path.GetFullPath(envPath + "/mLink.json");
                if (File.Exists(path))
                {
                    using (StreamReader sr = new StreamReader(path))
                    {
                        var str = sr.ReadToEnd();
                        config = JsonConvert.DeserializeObject<Config>(str);
                        if (config == null)
                        {
                            throw new Exception("config file format error");
                        }
                    }
                }
                else
                {
                    throw new Exception("config file was not found");
                };

                // start exe
                isCheck = config.autoStart;
                var pathCombine = config.location.Split(' ');
                var p1 = new List<string>();
                foreach (var p in pathCombine)
                {
                    if (p != "")
                    {
                        p1.Add(p);
                    }
                }
                //pathCombine = pathCombine.Where(p => p != "").ToArray();
                if (config.location == "notepad.exe")
                {
                    _exePath = config.location;
                }
                else
                {
                    _exePath = Path.GetFullPath(envPath + "/" + p1[0]);
                }
                if (p1.Count > 1)
                {
                    _exeArgs = "";
                    for (var i = 1; i < p1.Count; ++i)
                    {
                        _exeArgs += p1[i].ToString();
                        _exeArgs += " ";
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        void killOld()
        {
            try
            {
                var fileName = Path.GetFileNameWithoutExtension(_exePath);
                killExe(fileName, _exePath);
                var thisFileFullName = Path.GetFullPath(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
                var thisFileName = Path.GetFileNameWithoutExtension(thisFileFullName);
                killExe(thisFileName, thisFileFullName);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        void startWebSocket()
        {
            // start websocket
            _server = new WebSocketServer(config.port);
            _server.AddWebSocketService<WSCommander>("/commander");
            _server.Start();
        }

        void startHttp()
        {
            // start file server
            var htmlPath = Environment.CurrentDirectory + "/" + config.mainPage;
            htmlPath = Path.GetDirectoryName(htmlPath);
            HttpFileServer.getInstance().start(config.port + 1, htmlPath);
        }

        void init()
        {
            try
            {
                loadConfig();
                killOld();
                //startWebSocket();
                //startHttp();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        internal void tick()
        {
            checkState();
            //TODO
        }

        public void openBrowser()
        {
            var fileName = Path.GetFileName(config.mainPage);
            System.Diagnostics.Process.Start("http://localhost" + ":" + (config.port+1) + "/"+ fileName);
        }

        

        private void checkState()
        {
            if (!isCheck) return;
            if (isRunning())
                if (_proc != null)
                {
                    try
                    {
                        if (_proc.HasExited || !_proc.Responding)
                        {
                            _proc = null;
                        }
                    }
                    catch (Exception ex)
                    {
                        _proc = null;
                    }
                }

            if (_proc == null)
            {
                if (_server != null)
                {
                    _server.WebSocketServices.Broadcast(@"state:::stop");
                }
                start();
            }
        }

        private bool isRunning()
        {
            if (_proc != null)
            {
                try
                {
                    if (_proc.HasExited || !_proc.Responding)
                    {
                        _proc = null;
                    }
                }
                catch (Exception ex)
                {
                    _proc = null;
                }
            }

            return _proc != null;

        }

        public void start()
        {
            if (_proc != null) return;
            isCheck = true;
            try
            {
                _proc = new Process();
                _proc.StartInfo.FileName = _exePath;


                if (_exeArgs != "")
                {
                    _proc.StartInfo.Arguments = _exeArgs;
                }
                

                _proc.StartInfo.RedirectStandardOutput = true;
                _proc.StartInfo.RedirectStandardError = true;
                _proc.StartInfo.UseShellExecute = false;
                _proc.StartInfo.CreateNoWindow = true;
                _proc.Start();
                _proc.OutputDataReceived += _proc_OutputDataReceived;
                _proc.ErrorDataReceived += _proc_ErrorDataReceived; ;
                _proc.BeginOutputReadLine();
                _proc.BeginErrorReadLine();
                if (_server != null)
                {
                    _server.WebSocketServices.Broadcast(@"state:::started");
                }
            }
            catch (Exception ex)
            {
                _proc = null;
                Console.WriteLine(ex);
                if (_server != null)
                {
                    _server.WebSocketServices.Broadcast(@"state:::stop");
                    _server.WebSocketServices.Broadcast(string.Format("log:::{0}", ex.ToString()));
                }
            }
        }

        private void _proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if(_proc != null && _server != null)
            {
                _server.WebSocketServices.Broadcast(string.Format("log:::error:{0}", e.Data));
            }
        }

        private void _proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (_proc != null && _server != null)
            {
                _server.WebSocketServices.Broadcast(string.Format("log:::{0}", e.Data));
            }
        }

        public void toogle()
        {
            if (_proc == null)
            {
                this.start();
            }
            else
            {
                this.stop();
            }
        }

        public void stop()
        {
            if (_proc == null) return;
            try
            {
                _proc.Kill();
                _proc = null;
                isCheck = false;
            }
            catch (Exception ex)
            {
                _proc = null;
                Console.WriteLine(ex);
                if(_server != null)
                {
                    _server.WebSocketServices.Broadcast(string.Format("log:::{0}", ex.ToString()));
                }
            }
            if (_server != null)
            {
                _server.WebSocketServices.Broadcast(@"state:::stop");
            }
        }


        public void exit()
        {
            this.stop();
            if(_server != null) _server.Stop();
            HttpFileServer.getInstance().exit();
        }

        internal bool getState()
        {
            return _proc != null;
        }
    }
}
